/*
 * User.cpp
 *
 *  Created on: 2013. 10. 4.
 *      Author: the2
 */

#include "User.h"
#include "Static.h"

#include "Ack.h"

#include "MemCached.h"
#include "MemResultSet.h"

namespace FriendsServer {

User::User() :
		_socialId(0),
		_userSeq(0),
		_heartCount(0),
		_newsCount(0),
		_isRankView(0),
		_isRankReward(0),
		_rankConfirmTime(0),
		_myRank(-1),
		_friendRankCount(0),
		_inviteCount(0),
		_bufftime(0),
		_bKilled(false),
		_channelKey(0)
{
	_monitorTimer = new ::Dispatcher::Timer();
}

User::~User()
{
	delete _monitorTimer;
}

void
User::Initialize(int channelKey)
{
	_socialId = 0;
	_userSeq = 0;
	_heartCount = 0;
	_newsCount = 0;
	_isRankView = 0;
	_isRankReward = 0;
	_rankConfirmTime = 0;
	_myRank = -1;
	_friendRankCount = 0;
	_inviteCount = 0;
	_bufftime = 0;
	_bKilled = false;

	_userInfo.Initialize();

	_clsFriendsTree.clear();
	_clsDeleteFriendsTree.clear();
	_clsNoAppTree.clear();
	_clsSocialFriendsTree.clear();
	_clsRankingTree.clear();
	_clsRankingVector.clear();
	_clsThisWeekFriendTree.clear();

	_channelKey = channelKey;
}

void User::OnResponse(::Database::Command * pCommand) {}
void User::OnError(::Database::Command * pCommand, const char* err, int errCode) {}
void User::OnTime()
{
	if (false == _bKilled)
		return;

	Static::Instance().EraseUser(_userSeq);
}
void User::SetTimer()
{
	if (true == _bKilled)
	{
		return;
	}
	_bKilled = true;
	_monitorTimer->SetTimer(1000 * 10 * 30, std::bind( & User::OnTime, this));
	//_monitorTimer->SetTimer(5000, std::bind( & User::OnTime, this));
}

void User::SetRandomTime()
{
	if (true == _bKilled)
		return;

	std::string key = "location_" + std::to_string(_userSeq);
	Memcache->Delete(key.c_str(), key.size(), 0);

	_bKilled = true;
//	int time = rand() % 30;
//	int time2 = ( 1000 * 60 ) * ( 30 + time) ;
	_monitorTimer->SetTimer(1000 * 10 * 30, std::bind( & User::OnTime, this));
}

void User::KillTimer() { _bKilled = false; }

time_t
User::GetTestInitTime(time_t curTime)
{
	struct tm * tm;
	tm = localtime(&curTime);

	switch(Static::Instance().rankCycle)
	{
	case 60*10 : // 10분
	if (tm->tm_min >= 0 &&  tm->tm_min < 10 )
		tm->tm_min = 0;
	if (tm->tm_min >= 10 && tm->tm_min < 20)
		tm->tm_min = 10;
	if (tm->tm_min >= 20 && tm->tm_min < 30)
		tm->tm_min = 20;
	if (tm->tm_min >= 30 && tm->tm_min < 40)
		tm->tm_min = 30;
	if (tm->tm_min >= 40 && tm->tm_min < 50)
		tm->tm_min = 40;
	if (tm->tm_min >= 50)
		tm->tm_min = 50;
	break;

	case 60*20 : // 20분
	if (tm->tm_min >= 0 &&  tm->tm_min < 20 )
		tm->tm_min = 0;
	if (tm->tm_min >= 20 && tm->tm_min < 40)
		tm->tm_min = 20;
	if (tm->tm_min >= 40)
		tm->tm_min = 40;
	break;

	case 60*30 : // 30분
	if (tm->tm_min >= 0 &&  tm->tm_min < 30 )
		tm->tm_min = 0;
	if (tm->tm_min >= 30)
		tm->tm_min = 30;
	break;

	case 86400 : // 1 Day
		if (12 > tm->tm_hour)
		{
			curTime -= Static::Instance().rankCycle;
			tm = localtime(&curTime);
		}
		tm->tm_hour	= 12;
		tm->tm_min	= 0;
		tm->tm_sec	= 0;
		tm->tm_isdst	= 0;

		return mktime(tm);

	default : // 60분
		tm->tm_min		= 0;
	}

	tm->tm_sec      = 0;
	tm->tm_isdst    = 0;

	return mktime(tm);
}

time_t
User::GetWeekInitTime()
{
	if(true == Static::Instance().is_test) // test code
		return GetTestInitTime(time(NULL));

	time_t curTime;
	struct tm * stCurrent;

	time(&curTime);

	stCurrent = localtime(&curTime);

	int addSec = 0;

	switch (stCurrent->tm_wday)
	{
	case 1:
		if (12 > stCurrent->tm_hour)
			addSec = 7 * 3600 * 24;
		else
			addSec = 0 * 3600 * 24;
		break;
	case 2: addSec = 1 * 3600 * 24; break;
	case 3: addSec = 2 * 3600 * 24; break;
	case 4: addSec = 3 * 3600 * 24; break;
	case 5: addSec = 4 * 3600 * 24; break;
	case 6: addSec = 5 * 3600 * 24; break;
	case 0: addSec = 6 * 3600 * 24; break;
	}

	time_t initTime = curTime - addSec;
	struct tm * stInit = localtime(&initTime);

//	stInit->tm_hour	= 0;
	stInit->tm_hour	= 12;
	stInit->tm_min	= 0;
	stInit->tm_sec	= 0;
	stInit->tm_isdst	= 0;

	return mktime(stInit);
}

void
User::SetUserSeq(const uint32_t userSeq)
{
	_userSeq = userSeq;
}
uint32_t
User::AddHeartCount(const uint32_t constCount, const uint32_t heartCount)
{
	if(constCount < _heartCount + heartCount)
		_heartCount = constCount;
	else
		_heartCount += heartCount;

	if(constCount < _heartCount)
		_heartCount = constCount;

	return _heartCount;
}

uint32_t
User::SubtractHeartCount(const uint32_t heartCount)
{
	_heartCount -= heartCount;
	if(0 > _heartCount)
		_heartCount = 0;

	return _heartCount;
}

int
User::AddFriendAndInsertDB_Kakao(const uint64_t friendSocialid, uint32_t * frUserSeq)
{
	int userseq = 0;
	int shard_db_type = 0;
	{
		Database::ResultSet res = Database::Execute(2, "call sp_select_info_user_seq('", friendSocialid,"')");
		if (0 != res.GetSQLError())
		{
			LOG(DEV, "sp_select_info_user_seq errno : ", errno );
			return ACK_DB_ERROR;
		}

		for(auto itr = res.begin(); itr != res.end(); itr++)
		{
			userseq = itr->getInt("seq");
			shard_db_type = itr->getInt("shard_db_type");
		}
	}

	Database::ResultSet res = Database::Execute(this->GetUserInfo().shard_db_type, "call sp_select_info_friends(",
			this->_userSeq,", '", friendSocialid,"',", userseq, ",", shard_db_type, ")");
	if (0 != res.GetSQLError())
	{
		LOG(DEV, "sp_socialfriendsinfo errno : ", errno );
		return ACK_DB_ERROR;
	}

	for(auto itr = res.begin(); itr != res.end(); itr++)
	{
		User::FriendInfo friendInfo;

		* frUserSeq = itr->getInt("fr_user_seq");
		friendInfo.heartTime = itr->getInt("send_heart_date");
		friendInfo.buffTime = itr->getInt("bufftime");
		friendInfo.connectReqTime = itr->getInt("connect_req_time");
		friendInfo.friendDelTime = itr->getInt("friend_delete_time");
		friendInfo.friendType = 1;
		friendInfo.shard_db_type = shard_db_type;
		if(0 != *frUserSeq && 0 == friendInfo.friendDelTime)
		{
			this->_clsSocialFriendsTree[friendSocialid] = *frUserSeq;
			this->_clsFriendsTree[*frUserSeq] = friendInfo;
		}

		if(0 != *frUserSeq && 0 < friendInfo.friendDelTime)
			this->_clsDeleteFriendsTree[*frUserSeq] = friendInfo.friendDelTime;
	}
	return ACK_OK;
}

int User::SetGameUserSeq()
{
	Database::ResultSet res = Database::Execute(this->GetUserInfo().shard_db_type, "call sp_select_info_friends_InGame(", this->_userSeq, ")");
	if (0 != res.GetSQLError())
	{
		return ACK_DB_ERROR;
	}

	for(Database::ResultSet::iterator itr = res.begin(); itr != res.end(); itr++)
	{
		User::FriendInfo friendInfo;
		uint32_t frUserSeq = 0;
		frUserSeq					= itr->getInt("fr_user_seq");
		friendInfo.socialID			= itr->getInt64("fr_social_id");
		friendInfo.heartTime		= itr->getInt("send_heart_date");
		friendInfo.buffTime			= itr->getInt("buffusetime");
		friendInfo.connectReqTime	= itr->getInt("connect_req_time");
		friendInfo.friendDelTime	= itr->getInt("friend_delete_time");
		friendInfo.shard_db_type	= itr->getInt("shard_db_type");
		friendInfo.friendType		= 2;

		if (0 == friendInfo.friendDelTime)
			this->_clsFriendsTree[frUserSeq] = friendInfo;

		if (0 < friendInfo.friendDelTime)
			this->_clsDeleteFriendsTree[frUserSeq] = friendInfo.friendDelTime;
	}

	return ACK_OK;
}

void User::SetNoAppFriends()
{
	Database::ResultSet res = Database::Execute(this->GetUserInfo().shard_db_type, "call sp_select_info_friends_noapp(", this->_userSeq, ")");
	if (0 != res.GetSQLError())
	{
		return;
	}

	if (res.end() == res.begin())
	{
		return;
	}

	for(Database::ResultSet::iterator itr = res.begin(); itr != res.end(); itr++)
	{
		this->_clsNoAppTree[itr->getString("fr_social_id").c_str()] = itr->getInt("invite_time");
	}
}

int User::AddInviteNoAppFriend(const std::string & socialid)
{
	time_t curtime = time(NULL);
	Database::ResultSet res = Database::Execute(this->GetUserInfo().shard_db_type, "call sp_insert_info_friends_noapp(",
			this->_userSeq, ", '", socialid.c_str(), "',", curtime + (ONE_DAY * ONE_MON), ")");
	if (0 != res.GetSQLError())
	{
		return ACK_DB_ERROR;
	}

	auto noAppitr = this->_clsNoAppTree.find(socialid);
	if(this->_clsNoAppTree.end() == noAppitr)
	{
		this->_clsNoAppTree[socialid] = curtime + (ONE_DAY * ONE_MON);
		return ACK_OK;
	}

	if (curtime - noAppitr->second < (ONE_DAY * ONE_MON))
		return ACK_TIME_LEFT;

	noAppitr->second = curtime + (ONE_DAY * ONE_MON);
	return ACK_OK;
}

int
User::SendNewsToFriend(const uint32_t & frseq, const int & type, const int & heart_count)
{
	User::FRIENDSTREE::iterator fritr = _clsFriendsTree.find(frseq);
	if(_clsFriendsTree.end() == fritr)
	{
		return ACK_EXIST_USER;
	}

	Database::ResultSet res = Database::Execute(fritr->second.shard_db_type, "call sp_insert_data_news(", this->_userSeq, ",",
				frseq, ",", type, ",", heart_count, ",'", this->GetUserInfo().nickName.c_str(), "','", this->GetUserInfo().profileUrl.c_str(), "')" );

	if (0 != res.GetSQLError())
	{
		return ACK_DB_ERROR;
	}

	return ACK_OK;
}

int
User::SendNewsToFriend_Invite(const uint32_t shard_db_type, const uint32_t & frseq, const int & type, const int & heart_count)
{
	Database::ResultSet res = Database::Execute(shard_db_type, "call sp_insert_data_news(", this->_userSeq, ",",
				frseq, ",", type, ",", heart_count, ",'", this->GetUserInfo().nickName.c_str(), "','", this->GetUserInfo().profileUrl.c_str(), "')" );

	if (0 != res.GetSQLError())
	{
		return ACK_DB_ERROR;
	}

	return ACK_OK;
}

int
User::AddFriendAndInsertDB_InGame(const uint32_t frseq, const uint64_t & news_seq)
{
	User::FriendInfo frinfo;
	{
		Database::ResultSet res = Database::Execute(2, "call sp_select_info_login_socialid (", frseq ,")");
		if (0 != res.GetSQLError())
			return ACK_DB_ERROR;

		if (res.end() == res.begin())
			return ACK_NO_DATA;

		for(Database::ResultSet::iterator itr = res.begin(); itr != res.end(); itr++)
		{
//			frinfo.socialID = itr->getString("socialid").c_str();
			frinfo.socialID = itr->getInt64("socialid");
			frinfo.shard_db_type = itr->getInt("shard_db_type");
		}
	}


	{
		// 나에게 친구 정보 insert
		Database::ResultSet res = Database::Execute(this->GetUserInfo().shard_db_type, "call sp_insert_info_friends ('",
//				frinfo.socialID.c_str() ,"',", this->_userSeq, ",", frseq, ",", frinfo.shard_db_type, ")");
				frinfo.socialID ,"',", this->_userSeq, ",", frseq, ",", frinfo.shard_db_type, ")");
		if (0 != res.GetSQLError())
			return ACK_DB_ERROR;
	}

	{
		// 친구에게 내 정보 insert
		Database::ResultSet res = Database::Execute(frinfo.shard_db_type, "call sp_insert_info_friends ('",
//				this->GetSocialId().c_str() ,"',", frseq, ",", this->_userSeq, ",", this->GetUserInfo().shard_db_type, ")");
				this->GetSocialId() ,"',", frseq, ",", this->_userSeq, ",", this->GetUserInfo().shard_db_type, ")");
		if (0 != res.GetSQLError())
			return ACK_DB_ERROR;
	}

	Database::ResultSet res = Database::Execute(this->GetUserInfo().shard_db_type, "call sp_update_data_news_status (",
			news_seq ,",", "1)");
	if (0 != res.GetSQLError())
		return ACK_DB_ERROR;

	frinfo.friendType = 2;
	this->_clsFriendsTree[frseq] = frinfo;

	return ACK_OK;
}

int
User::SetBuffTime(const uint32_t & target_seq, const uint32_t & bufftime, const int type)
{
	auto iter = this->_clsFriendsTree.find(target_seq);
	if(this->_clsFriendsTree.end() == iter)
		return ACK_EXIST_USER;

	iter->second.buffTime = time(NULL) + ONE_DAY;

	_bufftime = bufftime;

	return ACK_OK;
}

std::vector<std::pair<uint32_t,uint32_t>>
User::MapGreaterSort(LASTWEEKRANKINGTREE &map)
{
	std::vector<std::pair<uint32_t,uint32_t> > vec(map.begin(), map.end());
	std::sort(vec.begin(), vec.end(), compare_pair_second<std::greater>());

	return vec;
}

void
User::MapGreaterSort()
{
		LOG(DEV, "ranking vector size ", _clsRankingVector.size());
	_clsRankingVector.assign(_clsRankingTree.begin(), _clsRankingTree.end());
		LOG(DEV, "ranking vector size ", _clsRankingVector.size());
	std::sort(_clsRankingVector.begin(), _clsRankingVector.end(), compare_pair_second<std::greater>());
}

int16_t
User::GetMyRanking()
{
	time_t initTime = GetWeekInitTime();
	time_t initTime2 = 0;
	if (true == Static::Instance().is_test)
		initTime2 = initTime - Static::Instance().rankCycle;
	else
		initTime2 = initTime - (7 * 3600 * 24);

	if (GetRankConfirmTime() > static_cast<uint32_t>(initTime))
		return ACK_OK;


	LOG(DEV, "userseq=", _userSeq, " ranking tree size[", GetRankingTree().size(), "]");

	int iFailureCountToGetFriendScore = 0;
	std::for_each(GetFriendsTree().begin(), GetFriendsTree().end(), [&](User::FRIENDSTREE::value_type & pair) {

		MemCachedData::Score score;
		if ( ACK_OK != Memcache->GetScore(pair.first, score) )
		{
			++iFailureCountToGetFriendScore;
		}			

		LOG(DEV, "userseq=", pair.first, "last regtime[", score.last_reg_date,"]", "last 스코어=", score.lastweek);
		LOG(DEV, "userseq=", pair.first, "cur regtime[", score.reg_date,"]", "cur 스코어=", score.curweek);

		// 2주 이상 경과한 경우 모든 데이터 초기화
		if (initTime2 > score.last_reg_date)
			score.lastweek = 0;
		if (initTime2 > score.reg_date)
			score.curweek = 0;
		if (score.last_reg_date != score.reg_date && initTime > score.reg_date)
		{
			score.last_reg_date = score.reg_date;
			score.lastweek = score.curweek;
		}

		if (initTime2 <= score.last_reg_date && score.last_reg_date < initTime)
			GetRankingTree()[pair.first] = score.lastweek;

		LOG(DEV, "userseq=", pair.first, "last regtime[", score.last_reg_date,"]", "last 스코어=", score.lastweek);
		LOG(DEV, "userseq=", pair.first, "cur regtime[", score.reg_date,"]", "cur 스코어=", score.curweek);
	});

	// 참여 인원 수 구하기
	_friendRankCount = _clsRankingTree.size();

	MemCachedData::Score score;
	int eRes = Memcache->GetScore(_userSeq, score);
	if( ACK_OK != eRes ) // 실패라면, ...
	{
		//-------------------------------------------------------------------------------------------------------------------
		// 지난 주 PvP 랭킹 등수 data가 소멸(내부적으로 0으로 set)되어 보상을 못 받게 되는 오류, Fixed by spencer(2014.06.20)

		// 상기에서 GetScore()에서 실패가 발생한다면, 하기의 if 문이 거짓이 되어서 _myRank = 0 이 된다.
		// 그리고 이것이 DB에 저장(myRank, 갱신 시간)된다. 재접속을 하게되면 갱신된 시간으로 인해서 함수 초반에서 return되어서
		// 잘못된 _myRank(0)이 수정되질 못하고 일주일간 지속된다.

		// => GetScore()가 실패할 경우에 대한 에러 처리를 보완
		//-------------------------------------------------------------------------------------------------------------------
		LOG(ERR, "GetMyRanking()> Failed to Memcache->GetScore()> myRank> _userSeq: ", _userSeq, ", eRes: ", eRes);
		return ACK_DB_ERROR;
	}	

	if (initTime2 <= score.last_reg_date && score.last_reg_date < initTime)
	{
		GetRankingTree()[_userSeq] = score.lastweek;

		// 내 등수 구하기
		MapGreaterSort();
		auto it = std::find_if(_clsRankingVector.begin(), _clsRankingVector.end(), match_first<uint32_t>(_userSeq));

		if (it != _clsRankingVector.end())
		{
			// found
			_myRank = std::distance(_clsRankingVector.begin(), it) + 1;
			LOG(DEV, "std::distance 내 랭킹 : ", _myRank);
		}
	}
	else
	{
		_myRank = 0;
	}

	LOG(DEV, "userseq = ", GetUserSeq(), " 내 랭킹 = ", _myRank, " 내 점수 = ", score.lastweek);

	//-------------------------------------------------------------------------------------------------------------------
	// 지난 주 PvP 랭킹 친구수 최소 인원 5명 미만으로 되어서 보상을 못받게 되는 오류, Fixed by spencer(2014.06.20)

	// 친구에 대한 GetScore()에서 실패가 발생하면, 친구 카운트가 증가되질 못한다.
	// 그런데 실패한 횟수로 인해서 최종적으로 5명 미만으로 산출되고 그리고 아래쪽의 등수는 정상적으로 처리되어서 최종 DB 저장 처리가 된다.
	// 재접속을 하게되면 갱신된 시간으로 인해서 함수 초반에서 return되어서 잘못된 친구수가 계속 유지되는 문제점이 있다.
	// 이럴 경우를 검사해서 발생 시에는 DB 저장을 하지 않는다. 그래서 다음 재접속시 정상적으로 갱신될 수 있는 기회를 준다.

	// => GetScore()가 실패할 경우에 대한 에러 처리를 보완
	//-------------------------------------------------------------------------------------------------------------------
	enum { eMinFriendCount = 5, };
	if ( eMinFriendCount > _friendRankCount && eMinFriendCount <= (iFailureCountToGetFriendScore+_friendRankCount) )
	{ // 산출된 친구 수가 5명 미만이고 실패된 숫자와 합한 것이 5명 이상일 경우에는, 실패로 인해서 보상 가능 친구수가 채워지지 않은 것일 수 있다.
		LOG(ERR, "GetMyRanking()> Failed to Memcache->GetScore()> FriendCount> _userSeq: ", _userSeq, ", _friendRankCount: ", _friendRankCount, " iFailureCountToGetFriendScore: ", iFailureCountToGetFriendScore);
		return ACK_DB_ERROR;

	}	
	
	SetRankConfirmTime(time(NULL));
	FlushUserInfo();
	
	return ACK_OK;
}

int
User::FlushUserInfo()
{
	Database::ResultSet res = Database::Execute(1,
			"call sp_update_info_social(",
			this->GetUserSeq(), ",",
			_userInfo.isHeartBlock, ",",
			_userInfo.isInviteBlock, ",",
			_userInfo.isProfileBlock, ",",
			_userInfo.gender, ",",
			_userInfo.isGenderOpen, ",",
			"'", _userInfo.birthday.c_str(), "',",
			_userInfo.isBirthdayOpen, ",",
			this->GetRankConfirmTime(), ",",
			this->GetInviteCount(), ",",
			this->GetHeartCount(), ",",
			_myRank, ",",
			_friendRankCount,
			")");

	if (0 != res.GetSQLError())
	{
		LOG(ERR, "FlushUserInfo errno:", errno);
		return ACK_DB_ERROR;
	}

	return ACK_OK;
}

bool
User::GetIsAllow()
{
	return (GAME_FRIEND_MAX - this->GetFriendsTree().size()) > 0 ? true:false;
}

int
User::DeleteFriend(const uint32_t shard_db_type, const uint32_t mySeq, const uint32_t friendSeq)
{
	auto fritr = GetFriendsTree().find(friendSeq);
	if(GetFriendsTree().end() == fritr)
	{
		LOG(DEV, "not found user gid : ", friendSeq);
//		return ACK_EXIST_USER;

		return ACK_OK;	// Client 가 요청한 seq 중에 탈퇴 유저가 있을수 있기 때문에 ACK_OK 전달.
	}

	GetFriendsTree().erase(friendSeq);

	// 내 친구 삭제
	Database::ResultSet res = Database::Execute(shard_db_type, "call sp_delete_info_friends(", mySeq, ",", friendSeq, ")");
	if (0 != res.GetSQLError())
	{
		return ACK_DB_ERROR;
	}

	// 친구 삭제 트리에 추가
	GetDeleteFriendsTree()[friendSeq] = time(NULL);

	// 내 친구 에게 나 삭제
	Database::Execute(fritr->second.shard_db_type, "call sp_delete_info_friends(", friendSeq, ",", mySeq , ")");

	return ACK_OK;
}

} /* namespace FriendsServer */
